#include "movierenderer.h"

#include "AudioRenderer/audioframe.h"
#include "AudioRenderer/audiorenderer.h"
#include "AudioRenderer/audiorendererfactory.h"
#include "videoframe.h"
#include "Common/numericoperations.h"

#include <GL/glu.h>
#include <math.h>
#include <iostream>
#include <iomanip>

const float OSD_FADE_SPEED = 5.f;

using namespace std;

MovieRenderer::MovieRenderer(int width, int height)
: m_pAudioRenderer(NULL)
, m_FrameRenderer(width, height)
, m_ShowOsd(false)
, m_Paused(true)
, m_ShowDebugInfo(false)
, m_WindowWidth(0)
, m_WindowHeight(0)
, m_OsdWidth(0)
, m_OsdHeight(0)
, m_OsdOffsetX(0)
, m_VisibleOsdPercentage(0.f)
, m_AudioClock(0.0)
, m_VideoClock(0.0)
{
    setWindowSize(width, height);
    m_DebugInfo.setSize(16);

    m_pAudioRenderer = AudioRendererFactory::create(AudioRendererFactory::OPENAL_OUTPUT);
}

MovieRenderer::~MovieRenderer()
{
    delete m_pAudioRenderer;
}

void MovieRenderer::setWindowSize(int width, int height)
{
    m_FrameRenderer.setWindowSize(width, height);
    m_SubtitleRenderer.setWindowSize(width, height);

    m_WindowWidth   = width;
    m_WindowHeight  = height;
    m_OsdWidth      = static_cast<int>(width * 0.75f);
    m_OsdHeight     = static_cast<int>(height / 5.f);
    m_OsdOffsetX    = static_cast<int>((width - m_OsdWidth) / 2.f);

    m_InfoDisplay.setSize(m_OsdWidth, m_OsdHeight);
}

bool MovieRenderer::setMovieFile(const std::string& movieFilename)
{
    if (m_MovieDecoder.initialize(movieFilename))
    {
        m_pAudioRenderer->setFormat(m_MovieDecoder.getAudioFormat());

        m_MovieDecoder.start();

        string subtitleFilename = movieFilename;
        subtitleFilename.replace(subtitleFilename.size() - 3, 3, "srt");
        m_SubtitleRenderer.loadSubtitle(subtitleFilename);
        m_InfoDisplay.setTitle(movieFilename);
        m_InfoDisplay.setCurrentTime(0);
        m_InfoDisplay.setTotalTime(m_MovieDecoder.getDuration());
        return true;
    }
    else
    {
        m_MovieDecoder.destroy();
        cerr << "Failed to open " << movieFilename << endl;
        return false;
    }
}

void MovieRenderer::render(uint64 elapsedTime)
{
    if (!m_Paused)
    {
        while (m_pAudioRenderer->hasBufferSpace())
        {
            AudioFrame audioFrame;
            if (m_MovieDecoder.decodeAudioFrame(audioFrame))
            {
                m_pAudioRenderer->queueFrame(audioFrame);
            }
            else
            {
                break;
            }
        }

        m_pAudioRenderer->flushBuffers();
        m_AudioClock = m_pAudioRenderer->getCurrentPts();

        int count = 0;
        VideoFrame videoFrame;
        while (m_VideoClock /* + average rendering time */ < m_AudioClock && count < 10)
        {
            ++count;
            if (m_MovieDecoder.decodeVideoFrame(videoFrame))
            {
                m_VideoClock = videoFrame.getPts();
                m_FrameRenderer.updateFrame(videoFrame);
            }
        }
    }

    m_FrameRenderer.renderFrame(elapsedTime);
    m_SubtitleRenderer.render(static_cast<uint64>(m_AudioClock * 1000));
    renderOsd(elapsedTime);
    renderDebugInfo();
}

void MovieRenderer::renderOsd(uint64 elapsedTime)
{
    float adjust = static_cast<float>(elapsedTime / OSD_FADE_SPEED);
    m_VisibleOsdPercentage += m_ShowOsd ? adjust : -adjust;
    NumericOperations::clip(m_VisibleOsdPercentage, 0.f, 100.f);

    if (m_VisibleOsdPercentage > 0)
    {
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity();
        gluOrtho2D(0, m_WindowWidth - 1, 0, m_WindowHeight - 1);
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();

        float percentage = sin(m_VisibleOsdPercentage / 100.f * 1.57079633f) * 100.f;
        glTranslatef(static_cast<float>(m_OsdOffsetX), (percentage * m_OsdHeight / 100.f) - m_OsdHeight, 0.f);
        m_InfoDisplay.setCurrentTime(static_cast<float>(m_AudioClock));
        m_InfoDisplay.setProgress(m_MovieDecoder.getProgress());
        m_InfoDisplay.render();

        glMatrixMode(GL_PROJECTION);
        glPopMatrix();
        glMatrixMode(GL_MODELVIEW);
        glPopMatrix();
    }
}

void MovieRenderer::renderDebugInfo()
{
    if (!m_ShowDebugInfo) return;

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, m_WindowWidth-1, 0, m_WindowHeight-1);

    stringstream ss;
    ss << "VideoClock: " << fixed << setprecision(3) << m_VideoClock << ends;

    m_DebugInfo.setText(ss.str());
    m_DebugInfo.setPosition(m_WindowWidth - 175.f, m_WindowHeight - 20);
    m_DebugInfo.render();

    ss.str("");
    ss << "AudioClock: " << fixed << setprecision(3) << m_AudioClock << ends;

    m_DebugInfo.setText(ss.str());
    m_DebugInfo.setPosition(m_WindowWidth - 175.f, m_WindowHeight - 35);
    m_DebugInfo.render();

    ss.str("");
    ss << "Diff: " << fixed << setprecision(3) << fabs(m_AudioClock - m_VideoClock) << ends;

    m_DebugInfo.setText(ss.str());
    m_DebugInfo.setPosition(m_WindowWidth - 175.f, m_WindowHeight - 50);
    m_DebugInfo.render();

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

void MovieRenderer::play()
{
    m_Paused = false;
    m_pAudioRenderer->play();
}

void MovieRenderer::pause()
{
    m_Paused = true;
    m_pAudioRenderer->pause();
}

void MovieRenderer::stop()
{
    m_MovieDecoder.stop();
    m_pAudioRenderer->stop();
    m_VideoClock = 0.0;
    m_AudioClock = 0.0;
}

void MovieRenderer::seek(int offset)
{
    m_MovieDecoder.seek(offset);
}

void MovieRenderer::adjustVolume(float offset)
{
    m_pAudioRenderer->adjustVolume(offset);
}

void MovieRenderer::toggleShader(const std::string& shaderFilename)
{
    m_FrameRenderer.toggleShader(shaderFilename);
}

void MovieRenderer::toggleDebugInfo()
{
    m_ShowDebugInfo = !m_ShowDebugInfo;
}

void MovieRenderer::adjustBrightness(float offset)
{
    m_FrameRenderer.adjustBrightness(offset);
}

void MovieRenderer::adjustGamma(float offset)
{
    m_FrameRenderer.adjustGamma(offset);
}

void MovieRenderer::adjustContrast(float offset)
{
    m_FrameRenderer.adjustContrast(offset);
}

void MovieRenderer::resetImageSettings()
{
    m_FrameRenderer.resetImageSettings();
}

void MovieRenderer::setStretchToFit(bool enabled)
{
    m_FrameRenderer.setStretchToFit(enabled);
}

void MovieRenderer::flipHorizontal()
{
    m_FrameRenderer.flipHorizontal();
}

void MovieRenderer::flipVertical()
{
    m_FrameRenderer.flipVertical();
}

void MovieRenderer::toggleOsd()
{
    m_ShowOsd = !m_ShowOsd;
}
